﻿using System;
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Runtime.Serialization;

namespace MailRuWrapper
{
    /// <summary>
    /// Базовый класс инкапсулирующий общий функционал
    /// </summary>
    public abstract class BaseWrapper
    {
        protected BaseWrapper()
        {
            LogLevel = LogLevel.AllErrors;
            IsPreValidationEnabled = true;
            ApiUri = "http://www.appsmail.ru/platform/api";
        }

        /// <summary>
        /// Глубина журналирования
        /// </summary>
        public LogLevel LogLevel { get; set; }

        /// <summary>
        /// Событие для журналирование работы
        /// </summary>
        public event Action<string> Log;

        protected void InvokeLog(string e, LogLevel level)
        {
            if (Log != null & LogLevel.HasFlag(level)) Log(e);
        }

        internal void ThrowValidation(string message)
        {
            InvokeLog(message, LogLevel.Validation);
            if (IsPreValidationEnabled)
                throw new MailRuWrapperValidationException(message);
        }

        ///<summary>
        /// Формат ответов API. По умолчанию JSON
        ///</summary>
        public ApiFormat Format { get; set; }

        ///<summary>
        /// Проводить ли валидацию данных перед выполнением запроса
        ///</summary>
        public bool IsPreValidationEnabled { get; set; }

        #region Params

        /// <summary>
        /// Идентификатор приложения
        /// </summary>
        public int AppID { get; protected set; }

        /// <summary>
        /// Ключ авторизации пользователя в приложении
        /// </summary>
        public String SessionKey { get; protected set; }

        /// <summary>
        /// Ключ аутентификации пользователя запустившего приложение на сервере приложения
        /// </summary>
        public String AuthenticationKey { get; protected set; }

        /// <summary>
        /// Идентификатор пользователя, запустившего приложение
        /// </summary>
        public String VID { get; protected set; }

        /// <summary>
        /// Идентификатор окна, в котором запущено приложение
        /// </summary>
        public string WindowID { get; protected set; }

        /// <summary>
        /// Путь до api, по умолчанию: http://www.appsmail.ru/platform/api
        /// </summary>
        [DefaultValue("http://www.appsmail.ru/platform/api")]
        public string ApiUri { get; set; }

        #endregion

        #region Api

        /// <summary>
        /// Функции, работающие с друзьями пользователя (социальным графом)
        /// </summary>
        public Friends Friends
        {
            get
            {
                return new Friends(this);
            }
        }

        /// <summary>
        /// Функции для получения/изменения пользовательской информации
        /// </summary>
        public Users Users
        {
            get
            {
                return new Users(this);
            }
        }

        /// <summary>
        /// Функции, работающие с фотоальбомами и отдельными фотографиями пользователей
        /// </summary>
        public Photos Photos
        {
            get
            {
                return new Photos(this);
            }
        }

        /// <summary>
        /// Функции, работающие с аудиозаписями и плейлистами пользователей
        /// </summary>
        public Audios Audios
        {
            get
            {
                return new Audios(this);
            }
        }

        /// <summary>
        /// Функции для работы с элементами блока "Что Нового"
        /// </summary>
        public Stream Stream
        {
            get
            {
                return new Stream(this);
            }
        }

        /// <summary>
        /// Функции для работы с Биллингом Платформы (прием платежей)
        /// </summary>
        public Payments Payments
        {
            get
            {
                return new Payments(this);
            }
        }

        #endregion

        /// <summary>
        /// Сформировать строку запроса
        /// </summary>
        /// <param name="method">Вызываемый метод</param>
        /// <param name="customParams">Дополнительные параметры</param>
        /// <returns>Http запрос</returns>
        internal virtual String GetRequestString(FuncType method, params string[] customParams)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Выполнить запрос
        /// </summary>
        /// <param name="request">Текст запроса</param>
        /// <returns>Ответ</returns>
        internal string GetResponse(string request)
        {
#if !SILVERLIGHT
            try
            {
                InvokeLog("Send request: " + request, LogLevel.Steps);
                var _webRequest = (HttpWebRequest) WebRequest.Create(request);
                _webRequest.Method = "GET";

                var _response = (HttpWebResponse) _webRequest.GetResponse();

                if (_response.StatusCode == HttpStatusCode.OK)
                {
                    using (var _reader = new StreamReader(_response.GetResponseStream()))
                    {
                        var _result = _reader.ReadToEnd();
                        InvokeLog("Get response: " + _result, LogLevel.Steps);
                        return _result;
                    }
                }
                InvokeLog("_response StatusCode: " + _response.StatusCode, LogLevel.Wrapper);
                throw new WebException("_response StatusCode: " + _response.StatusCode);
            }
            catch (Exception _ex)
            {
                InvokeLog("Request timeout exception", LogLevel.Wrapper);
                throw new WebException("Request timeout exception", _ex);
            }
#endif
            throw new NotSupportedException();
        }

        internal IAsyncResult BeginGetResponse(string request, AsyncCallback callback, object state)
        {
            InvokeLog("Send request: "+request, LogLevel.Steps);
            var _webRequest = (HttpWebRequest) WebRequest.Create(request);
            _webRequest.Method = "GET";
            return _webRequest.BeginGetResponse(callback, _webRequest);
        }

        internal AsyncResult EndGetResponse(IAsyncResult asyncResult)
        {
            var _webRequest = (HttpWebRequest)asyncResult.AsyncState;
            var _response = (HttpWebResponse)_webRequest.EndGetResponse(asyncResult);

            if (_response.StatusCode == HttpStatusCode.OK)
            {
                using (var _reader = new StreamReader(_response.GetResponseStream()))
                {
                    var _result = _reader.ReadToEnd();
                    InvokeLog("Get response: " + _result, LogLevel.Steps);
                    return new AsyncResult { Request = _webRequest.RequestUri.OriginalString, Response = _result};
                }
            }
            throw new WebException("Response StatusCode: " + _response.StatusCode);

        }

        /// <summary>
        /// Разобрать ответ
        /// </summary>
        /// <param name="response">Ответ</param>
        /// <param name="method">Тип метода</param>
        /// <param name="request">Запрос</param>
        /// <returns>Сформированный объект</returns>
        internal virtual object ParsResponse(string response, FuncType method, string request)
        {
            throw new NotImplementedException();
        }
    }

    /// <summary>
    /// Функции, работающие с друзьями пользователя (социальным графом)
    /// </summary>
    public sealed class Friends
    {
        private readonly BaseWrapper _baseWrapper;

        internal Friends(BaseWrapper baseWrapper)
        {
            _baseWrapper = baseWrapper;
        }

        #region GetAppUsers

#if !SILVERLIGHT
        /// <summary>
        /// Возвращает список идентификаторов друзей пользователя, у которых 
        /// установлено данное приложение. Также возможно возвращение анкетной информации 
        /// о пользователях при поднятии флага ext.
        /// </summary>
        /// <param name="uid">Идентификатор пользователя, для которого получаем друзей (у него должно быть установлено приложение)</param>
        /// <param name="ext">При установленном флаге ext, возвращает анкетную информацию о друзьях</param>
        public IUser[] GetAppUsers(string uid, bool ext = false)
        {
            var _request = _baseWrapper.GetRequestString(FuncType.FriendsGetAppUsers,
                                            uid.ToRequestParam("uid"),
                                            ext.ToRequestParam("ext"));

            var _result = _baseWrapper.GetResponse(_request);

            return _baseWrapper.ParsResponse(_result, FuncType.FriendsGetAppUsers, _request) as IUser[];

        }
#else
        #region Async GetAppUsers
        /// <summary>
        /// Возвращает список идентификаторов друзей пользователя, у которых 
        /// установлено данное приложение. Также возможно возвращение анкетной информации 
        /// о пользователях при поднятии флага ext.
        /// </summary>
        /// <param name="uid">Идентификатор пользователя, для которого получаем друзей (у него должно быть установлено приложение)</param>
        public IAsyncResult BeginGetAppUsers(AsyncCallback callback, bool ext = false, object state = null)
        {
            var _request = _baseWrapper.GetRequestString(FuncType.FriendsGetAppUsers,
                                            ext.ToRequestParam("ext"));
            return _baseWrapper.BeginGetResponse(_request, callback, state);
        }

        public IUser[] EndGetAppUsers(IAsyncResult asyncResult)
        {
            var _result = _baseWrapper.EndGetResponse(asyncResult);

            return _baseWrapper.ParsResponse(_result.Response, FuncType.FriendsGetAppUsers, _result.Request) as IUser[];
        }

        #endregion
#endif
        #endregion

        #region Get
#if !SILVERLIGHT
        /// <summary>
        /// Возвращает список идентификаторов друзей пользователя. Также возможно возвращение анкетной информации для получаемых друзей.
        /// </summary>
        /// <param name="uid">Идентификатор пользователя, для которого получаем друзей (у него должно быть установлено приложение)
        /// Если не указан, получаем для текущего пользователя</param>
        /// <param name="ext">При установленном флаге ext=true, метод возвращает анкетную информацию о друзьях</param> 
        public IUser[] Get(string uid, bool ext = false)
        {
            var _request = _baseWrapper.GetRequestString(FuncType.FriendsGet,
                                            uid.ToRequestParam("uid"),
                                            ext.ToRequestParam("ext"));
            var _result = _baseWrapper.GetResponse(_request);

            return _baseWrapper.ParsResponse(_result, FuncType.FriendsGet, _request) as IUser[];
        }
#else
        #region Async Get
        /// <summary>
        /// Возвращает список идентификаторов друзей пользователя. Также возможно возвращение анкетной информации для получаемых друзей.
        /// </summary>
        /// <param name="uid">Идентификатор пользователя, для которого получаем друзей (у него должно быть установлено приложение)
        /// Если не указан, получаем для текущего пользователя</param>
        /// <param name="ext">При установленном флаге ext=true, метод возвращает анкетную информацию о друзьях</param> 
        public IAsyncResult BeginGet(AsyncCallback callback, bool ext = false, object state = null)
        {
            var _request = _baseWrapper.GetRequestString(FuncType.FriendsGet,
                                            ext.ToRequestParam("ext"));
            return _baseWrapper.BeginGetResponse(_request, callback, state);
        }

        public IUser[] EndGet(IAsyncResult asyncResult)
        {
            var _result = _baseWrapper.EndGetResponse(asyncResult);

            return _baseWrapper.ParsResponse(_result.Response, FuncType.FriendsGet, _result.Request) as IUser[];
        }

        #endregion
#endif
        #endregion
    }

    /// <summary>
    /// Функции для получения/изменения пользовательской информации
    /// </summary>
    public sealed class Users
    {
        private readonly BaseWrapper _baseWrapper;

        internal Users(BaseWrapper baseWrapper)
        {
            _baseWrapper = baseWrapper;
        }

        #region IsAppUser
#if !SILVERLIGHT
        /// <summary>
        /// Проверка, установлено ли у пользователя приложение;
        /// </summary>
        /// <param name="uid">Идентификатор пользователя для которого проверяется установлено ли приложение</param>
        public bool IsAppUser(string uid)
        {
            var _request = _baseWrapper.GetRequestString(FuncType.UsersIsAppUser,
                                            uid.ToRequestParam("uid"));

            var _result = _baseWrapper.GetResponse(_request);

            return (bool)_baseWrapper.ParsResponse(_result, FuncType.UsersIsAppUser, _request);
        }
#else
        #region Async IsAppUser
        /// <summary>
        /// Проверка, установлено ли у пользователя приложение;
        /// </summary>
        /// <param name="uid">Идентификатор пользователя для которого проверяется установлено ли приложение</param>
        public IAsyncResult BeginIsAppUser(AsyncCallback callback, object state = null)
        {
            var _request = _baseWrapper.GetRequestString(FuncType.UsersIsAppUser);
            return _baseWrapper.BeginGetResponse(_request, callback, state);
        }

        public bool EndIsAppUser(IAsyncResult asyncResult)
        {
            var _result = _baseWrapper.EndGetResponse(asyncResult);

            return (bool)_baseWrapper.ParsResponse(_result.Response, FuncType.UsersIsAppUser, _result.Request);
        }

        #endregion
#endif
        #endregion

        #region GetInfo
#if !SILVERLIGHT
        /// <summary>
        /// Возвращает анкетную информацию о пользователях, доступную текущему пользователю. 
        /// Информация, которую текущий пользователь не может увидеть на странице 
        /// запрашиваемых пользователей не будет возвращена.
        /// </summary>
        /// <param name="uids">Идентификаторы пользователей, для которых мы получаем анкетную информацию</param> 
        public  IUser[] GetInfo(params string[] uids)
        {
            if (uids.Length > 200)
                _baseWrapper.ThrowValidation("GetInfo не может принимать более 200 идентификаторов в одном вызове");

            var _request = _baseWrapper.GetRequestString(FuncType.UsersGetInfo,
                                            uids.ToRequestParam("uids"));

            var _result = _baseWrapper.GetResponse(_request);

            return _baseWrapper.ParsResponse(_result, FuncType.UsersGetInfo, _request) as IUser[];
        }
#else
        #region Async GetInfo
        /// <summary>
        /// Возвращает анкетную информацию о пользователях, доступную текущему пользователю. 
        /// Информация, которую текущий пользователь не может увидеть на странице 
        /// запрашиваемых пользователей не будет возвращена.
        /// </summary>
        /// <param name="uids">Идентификаторы пользователей, для которых мы получаем анкетную информацию</param> 
        public IAsyncResult BeginGetInfo(AsyncCallback callback, object state = null, params string[] uids)
        {
            if (uids.Length > 200)
                _baseWrapper.ThrowValidation("BeginGetInfo не может принимать более 200 идентификаторов в одном вызове");

            var _request = _baseWrapper.GetRequestString(FuncType.UsersGetInfo,
                                            uids.ToRequestParam("uids"));

            return _baseWrapper.BeginGetResponse(_request, callback, state);
        }

        public IUser[] EndGetInfo(IAsyncResult asyncResult)
        {
            var _result = _baseWrapper.EndGetResponse(asyncResult);

            return _baseWrapper.ParsResponse(_result.Response, FuncType.UsersGetInfo, _result.Request) as IUser[];
        }

        #endregion
#endif
        #endregion

        #region HasAppPermission
#if !SILVERLIGHT
        /// <summary>
        /// Проверка, дал ли пользователь соответствующее разрешение для приложения или нет
        /// На данный момент поддерживаются разрешения:
        /// notice - разрешить присылать уведомления;
        /// news - разрешить публикацию новостей от приложения в блоке "Что нового?";
        /// widget - разрешить публикацию виджета на странице профиля пользователя;
        /// </summary>
        /// <param name="uid">Идентификатор пользователя для которого проверяется разрешение</param>
        /// <param name="param">Название запрашиваемого разрешения</param>
        public bool HasAppPermission(AppPermissionType param, string uid)
        {
            var _request = _baseWrapper.GetRequestString(FuncType.UsersHasAppPermission,
                                            param.ToRequestParam("ext_perm"),
                                            uid.ToRequestParam("uid"));

            var _result = _baseWrapper.GetResponse(_request);

            return (bool)_baseWrapper.ParsResponse(_result, FuncType.UsersHasAppPermission, _request);

        }
#else
        #region Async UsersHasAppPermission
        /// <summary>
        /// Проверка, дал ли пользователь соответствующее разрешение для приложения или нет
        /// На данный момент поддерживаются разрешения:
        /// notice - разрешить присылать уведомления;
        /// news - разрешить публикацию новостей от приложения в блоке "Что нового?";
        /// widget - разрешить публикацию виджета на странице профиля пользователя;
        /// </summary>
        /// <param name="uid">Идентификатор пользователя для которого проверяется разрешение</param>
        /// <param name="param">Название запрашиваемого разрешения</param>
        public IAsyncResult BeginHasAppPermission(AsyncCallback callback, AppPermissionType param = AppPermissionType.Notifications, object state = null)
        {
            var _request = _baseWrapper.GetRequestString(FuncType.UsersHasAppPermission,
                                            param.ToRequestParam("ext_perm"));
            return _baseWrapper.BeginGetResponse(_request, callback, state);
        }

        public bool EndHasAppPermission(IAsyncResult asyncResult)
        {
            var _result = _baseWrapper.EndGetResponse(asyncResult);

            return (bool)_baseWrapper.ParsResponse(_result.Response, FuncType.UsersHasAppPermission, _result.Request);
        }

        #endregion
#endif

        #endregion
    }

    /// <summary>
    /// Функции, работающие с фотоальбомами и отдельными фотографиями пользователей
    /// </summary>
    public sealed class Photos
    {
        private readonly BaseWrapper _baseWrapper;

        internal Photos(BaseWrapper baseWrapper)
        {
            _baseWrapper = baseWrapper;
        }

        #region GetAlbums
#if !SILVERLIGHT
        /// <summary>
        /// Возврашает информацию обо всех альбомах пользователя
        /// </summary>
        /// <param name="uid">Идентификатор пользователя, для которого получаем список альбомов (у него должно быть установлено приложение)</param>
        /// <param name="aids">Идентификаторы альбомов пользователя, о которых запрашивается информация</param>
        public IPhotoAlbum[] GetAlbums(string uid, string[] aids = null)
        {
            var _request = _baseWrapper.GetRequestString(FuncType.PhotosGetAlbums,
                                            uid.ToRequestParam("uid"),
                                            aids.ToRequestParam("aids"));

            var _result = _baseWrapper.GetResponse(_request);

            return _baseWrapper.ParsResponse(_result, FuncType.PhotosGetAlbums, _request) as IPhotoAlbum[];
        }
#else
        #region Async GetAlbums
        /// <summary>
        /// Возврашает информацию обо всех альбомах пользователя
        /// </summary>
        /// <param name="uid">Идентификатор пользователя, для которого получаем список альбомов (у него должно быть установлено приложение)</param>
        /// <param name="aids">Идентификаторы альбомов пользователя, о которых запрашивается информация</param>
        public IAsyncResult BeginGetAlbums(AsyncCallback callback, string[] aids = null, object state = null)
        {
            var _request = _baseWrapper.GetRequestString(FuncType.PhotosGetAlbums,
                                            aids.ToRequestParam("aids"));
            return _baseWrapper.BeginGetResponse(_request, callback, state);
        }

        public IPhotoAlbum[] EndGetAlbums(IAsyncResult asyncResult)
        {
            var _result = _baseWrapper.EndGetResponse(asyncResult);

            return _baseWrapper.ParsResponse(_result.Response, FuncType.PhotosGetAlbums, _result.Request) as IPhotoAlbum[];
        }

        #endregion
#endif
        #endregion

        #region Get
#if !SILVERLIGHT
        /// <summary>
        /// Возвращает информацию обо всех (или о выбранных) фотографиях в альбоме пользователя
        /// </summary>
        /// <param name="aid">Идентификатор альбома, в котором хранится фотография</param>
        /// <param name="uid">Идентификатор пользователя, для которого получаем информацию о фотографиях (у него должно быть установлено приложение)</param>
        /// <param name="pids">Идентификаторы фотографий, о которых запрашивается информация</param>
        public IPhoto[] Get(string aid, string uid, string[] pids = null)
        {
            var _request = _baseWrapper.GetRequestString(FuncType.PhotosGet,
                                            aid.ToRequestParam("aid"),
                                            uid.ToRequestParam("uid"),
                                            pids.ToRequestParam("pids"));

            var _result = _baseWrapper.GetResponse(_request);

            return _baseWrapper.ParsResponse(_result, FuncType.PhotosGet, _request) as IPhoto[];
        }
#else
        #region Async PhotosGet
        /// <summary>
        /// Возвращает информацию обо всех (или о выбранных) фотографиях в альбоме пользователя
        /// </summary>
        /// <param name="aid">Идентификатор альбома, в котором хранится фотография</param>
        /// <param name="uid">Идентификатор пользователя, для которого получаем информацию о фотографиях (у него должно быть установлено приложение)</param>
        /// <param name="pids">Идентификаторы фотографий, о которых запрашивается информация</param>
        public IAsyncResult BeginGet(AsyncCallback callback, string aid, string[] pids = null, object state = null)
        {
            var _request = _baseWrapper.GetRequestString(FuncType.PhotosGet,
                                            aid.ToRequestParam("aid"),
                                            pids.ToRequestParam("pids"));

            return _baseWrapper.BeginGetResponse(_request, callback, state);
        }

        public IPhoto[] EndGet(IAsyncResult asyncResult)
        {
            var _result = _baseWrapper.EndGetResponse(asyncResult);

            return _baseWrapper.ParsResponse(_result.Response, FuncType.PhotosGet, _result.Request) as IPhoto[];
        }

        #endregion
#endif
        #endregion
    }

    /// <summary>
    /// Функции, работающие с аудиозаписями и плейлистами пользователей
    /// </summary>
    public sealed class Audios
    {
        private readonly BaseWrapper _baseWrapper;

        internal Audios(BaseWrapper baseWrapper)
        {
            _baseWrapper = baseWrapper;
        }

        #region AudiosGet
#if !SILVERLIGHT
        /// <summary>
        /// Возвращает информацию о плейлисте пользователя (или о отдельных аудиозаписях)
        /// </summary>
        /// <param name="uid">Идентификатор пользователя, для которого получаем плейлист (у него должно быть установлено приложение)</param>
        /// <param name="mids">Идентификаторы аудиозаписей, о которых необходимо получить информацию</param>
        public IAudio[] Get(string uid, string[] mids = null)
        {
            var _request = _baseWrapper.GetRequestString(FuncType.AudiosGet,
                                            uid.ToRequestParam("uid"),
                                            mids.ToRequestParam("mids"));

            var _result = _baseWrapper.GetResponse(_request);

            return _baseWrapper.ParsResponse(_result, FuncType.AudiosGet, _request) as IAudio[];
        }
#else
        #region Async Get
        /// <summary>
        /// Возвращает информацию о плейлисте пользователя (или о отдельных аудиозаписях)
        /// </summary>
        /// <param name="uid">Идентификатор пользователя, для которого получаем плейлист (у него должно быть установлено приложение)</param>
        /// <param name="mids">Идентификаторы аудиозаписей, о которых необходимо получить информацию</param>
        public IAsyncResult BeginGet(AsyncCallback callback, string[] mids = null, object state = null)
        {
            var _request = _baseWrapper.GetRequestString(FuncType.AudiosGet,
                                            mids.ToRequestParam("mids"));
            return _baseWrapper.BeginGetResponse(_request, callback, state);
        }

        public IAudio[] EndGet(IAsyncResult asyncResult)
        {
            var _result = _baseWrapper.EndGetResponse(asyncResult);

            return _baseWrapper.ParsResponse(_result.Response, FuncType.AudiosGet, _result.Request) as IAudio[];
        }

        #endregion
#endif
        #endregion

        #region AudiosLinkAudio
#if !SILVERLIGHT
        /// <summary>
        /// Добавляет аудиозапись в плейлист пользователя;
        /// </summary>
        /// <param name="mid">Идентификатор добавляемой аудиозаписи</param>
        /// <returns>Добавлена ли запись</returns>
        public bool LinkAudio(string mid)
        {
            if (String.IsNullOrEmpty(_baseWrapper.SessionKey))
                throw new ArgumentNullException("SessionKey");

            var _request = _baseWrapper.GetRequestString(FuncType.AudiosLinkAudio,
                                            _baseWrapper.SessionKey.ToRequestParam("session_key"),
                                            mid.ToRequestParam("mid"));

            var _result = _baseWrapper.GetResponse(_request);

            return (bool)_baseWrapper.ParsResponse(_result, FuncType.AudiosLinkAudio, _request);
        }
#else
        #region Async LinkAudio
        /// <summary>
        /// Добавляет аудиозапись в плейлист пользователя;
        /// </summary>
        /// <param name="mid">Идентификатор добавляемой аудиозаписи</param>
        /// <returns>Добавлена ли запись</returns>
        public IAsyncResult BeginLinkAudio(AsyncCallback callback, string mid, object state = null)
        {
            var _request = _baseWrapper.GetRequestString(FuncType.AudiosLinkAudio,
                                            mid.ToRequestParam("mid"));

            return _baseWrapper.BeginGetResponse(_request, callback, state);
        }

        public bool EndLinkAudio(IAsyncResult asyncResult)
        {
            var _result = _baseWrapper.EndGetResponse(asyncResult);

            return (bool)_baseWrapper.ParsResponse(_result.Response, FuncType.AudiosLinkAudio, _result.Request);
        }

        #endregion
#endif
        #endregion
    }

    /// <summary>
    /// Функции для работы с элементами блока "Что Нового"
    /// </summary>
    public sealed class Stream
    {
        private readonly BaseWrapper _baseWrapper;

        internal Stream(BaseWrapper baseWrapper)
        {
            _baseWrapper = baseWrapper;
        }

        #region Publish
#if !SILVERLIGHT
        /// <summary>
        /// Публикует запись в блоке "Что нового?" пользователя. Приложение не может делать более 5 записей для пользователя в 24 часа.
        /// </summary>
        /// <param name="text">Основное содержимое записи "Что нового" в формате plaintext. 
        /// Внутри текста можно ставить ссылки на страницу приложения с возможность передачи 
        /// параметра с помощью специального тега: [url=state]...[/url]. Значение параметра 
        /// url, указанное в теге, будет передано в приложение в виде параметра state.
        /// Например при text="Я победил дракона с моими друзьями - 
        /// [url=invite]присоединяйся[/url] к нашей команде!" слово "присоединяйся" в записе 
        /// Что Нового будет ссылкой, и, при переходе по ней, в приложение будет передан 
        /// параметр state=invite, который можно использовать во внутренней логике.</param>
        /// <param name="post">Текст сообщения от имени пользователя, сопровождающий 
        /// данную запись "Что нового" в формате plaintext</param>
        /// <param name="img">Идентификатор картинки, сопровождающей текст. 
        /// Картинка должна быть предварительно загружена в специальный фотоальбом 
        /// разработчика "Мои приложения". Это можно сделать в настройках приложения, 
        /// пройдя по сылке "Добавить или выбрать изображение для информера"</param>
        public bool Publish(string text, string post = null, int img = 0)
        {
            var _request = _baseWrapper.GetRequestString(FuncType.StreamPublish,
                                               text.ToRequestParam("text"),
                                               post.ToRequestParam("post"),
                                               img.ToRequestParam("img"));
            var _result = _baseWrapper.GetResponse(_request);

            return (bool)_baseWrapper.ParsResponse(_result, FuncType.StreamPublish, _request);
        }
#else
        #region Async Publish

        /// <summary>
        /// Публикует запись в блоке "Что нового?" пользователя. Приложение не может делать более 5 записей для пользователя в 24 часа.
        /// </summary>
        /// <param name="text">Основное содержимое записи "Что нового" в формате plaintext. 
        /// Внутри текста можно ставить ссылки на страницу приложения с возможность передачи 
        /// параметра с помощью специального тега: [url=state]...[/url]. Значение параметра 
        /// url, указанное в теге, будет передано в приложение в виде параметра state.
        /// Например при text="Я победил дракона с моими друзьями - 
        /// [url=invite]присоединяйся[/url] к нашей команде!" слово "присоединяйся" в записе 
        /// Что Нового будет ссылкой, и, при переходе по ней, в приложение будет передан 
        /// параметр state=invite, который можно использовать во внутренней логике.</param>
        /// <param name="post">Текст сообщения от имени пользователя, сопровождающий 
        /// данную запись "Что нового" в формате plaintext</param>
        /// <param name="img">Идентификатор картинки, сопровождающей текст. 
        /// Картинка должна быть предварительно загружена в специальный фотоальбом 
        /// разработчика "Мои приложения". Это можно сделать в настройках приложения, 
        /// пройдя по сылке "Добавить или выбрать изображение для информера"</param>
        public IAsyncResult BeginPublish(AsyncCallback callback, string text, string post = null, int img = 0, object state = null)
        {
            var _request = _baseWrapper.GetRequestString(FuncType.StreamPublish,
                                               text.ToRequestParam("text"),
                                               post.ToRequestParam("post"),
                                               img.ToRequestParam("img"));
            return _baseWrapper.BeginGetResponse(_request, callback, state);
        }

        public bool EndPublish(IAsyncResult asyncResult)
        {
            var _result = _baseWrapper.EndGetResponse(asyncResult);

            return (bool)_baseWrapper.ParsResponse(_result.Response, FuncType.StreamPublish, _result.Request);
        }

        #endregion
#endif
        #endregion
    }

    /// <summary>
    /// Функции для работы с Биллингом Платформы (прием платежей)
    /// </summary>
    public sealed class Payments
    {
        private readonly BaseWrapper _baseWrapper;

        internal Payments(BaseWrapper baseWrapper)
        {
            _baseWrapper = baseWrapper;
        }

        #region OpenDialog
#if !SILVERLIGHT
        /// <summary>
        /// Вызывает окно оплаты услуги. Является частью Биллинга Платформы. Может быть вызвана как из клиентской части приложения, так и с сервера приложения.
        /// </summary>
        /// <param name="service_id">Идентификатор услуги, указанный разработчиком. Для данной услуги должен быть постоянным.</param>
        /// <param name="service_name">Название услуги в именительном падеже, указанное разработчиком (не более 40 символов). Для каждой услуги нужно определить постоянное 
        /// название (например: "5 золотых", "бутылка вина", "Кожаная броня 2-ого уровня"). В окне оплаты данной услуги фраза будет строится следующим образом: 
        /// "Купить: service_name". Название приложения включать в название услуги не нужно.</param>
        /// <param name="sms_price">Возможные значения - 1, 3, 5. Приблизительная стоимость услуги в уловных единицах для sms-платежа. Параметр обязателен, если услуга
        ///  может быть оплачена посредством sms-платежа.</param>
        /// <param name="other_price">Стоимость услуги в копейках. Параметр обязателен, если услуга может быть оплачена всеми доступными способами ввода (кроме sms-платежей).</param>
        /// <returns></returns>
        public bool OpenDialog(int service_id, string service_name, int other_price, int sms_price = 0)
        {
            if (String.IsNullOrEmpty(_baseWrapper.WindowID))
                throw new ArgumentNullException("WindowID");


            if (service_name.Length > 40)
                _baseWrapper.ThrowValidation("service_name не может быть более 40 символов");

            if (sms_price != 0 & sms_price != 1 & sms_price != 3 & sms_price != 5)
                _baseWrapper.ThrowValidation("Возможные значения параметра sms_price - 0, 1, 3, 5");

            if (other_price != 0 & (other_price > 5000000 | other_price < 100))
                _baseWrapper.ThrowValidation("Минимальное значение параметра other_price 100 (1 рубль), максимальное 5000000 (50 000 рублей)");

            var _request = _baseWrapper.GetRequestString(FuncType.PaymentsOpenDialog,
                                                         service_id.ToRequestParam("service_id"),
                                                         service_name.ToRequestParam("service_name"),
                                                         _baseWrapper.WindowID.ToRequestParam("window_id"),
                                                         other_price.ToRequestParam("other_price"),
                                                         sms_price.ToRequestParam("sms_price"));

            var _result = _baseWrapper.GetResponse(_request);

            return (bool) _baseWrapper.ParsResponse(_result, FuncType.PaymentsOpenDialog, _request);
        }
#else
        #region Async OpenDialogSms
        /// <summary>
        /// Вызывает окно оплаты услуги. Является частью Биллинга Платформы. Может быть вызвана как из клиентской части приложения, так и с сервера приложения.
        /// </summary>
        /// <param name="service_id">Идентификатор услуги, указанный разработчиком. Для данной услуги должен быть постоянным.</param>
        /// <param name="service_name">Название услуги в именительном падеже, указанное разработчиком (не более 40 символов). Для каждой услуги нужно определить постоянное 
        /// название (например: "5 золотых", "бутылка вина", "Кожаная броня 2-ого уровня"). В окне оплаты данной услуги фраза будет строится следующим образом: 
        /// "Купить: service_name". Название приложения включать в название услуги не нужно.</param>
        /// <param name="sms_price">Возможные значения - 1, 3, 5. Приблизительная стоимость услуги в уловных единицах для sms-платежа. Параметр обязателен, если услуга
        ///  может быть оплачена посредством sms-платежа.</param>
        /// <param name="other_price">Стоимость услуги в копейках. Параметр обязателен, если услуга может быть оплачена всеми доступными способами ввода (кроме sms-платежей).</param>
        /// <returns></returns>
        public IAsyncResult BeginOpenDialog(AsyncCallback callback, int service_id, string service_name, int other_price, int sms_price = 0, object state = null)
        {

            if (service_name.Length > 40)
                _baseWrapper.ThrowValidation("service_name не может быть более 40 символов");

            if (sms_price != 0 & sms_price != 1 & sms_price != 3 & sms_price != 5)
                _baseWrapper.ThrowValidation("Возможные значения параметра sms_price - 0, 1, 3, 5");

            if (other_price != 0 & (other_price > 5000000 | other_price < 100))
                _baseWrapper.ThrowValidation("Минимальное значение параметра other_price 100 (1 рубль), максимальное 5000000 (50 000 рублей)");

            var _request = _baseWrapper.GetRequestString(FuncType.PaymentsOpenDialog,
                                            service_id.ToRequestParam("service_id"),
                                            service_name.ToRequestParam("service_name"),
                                            _baseWrapper.WindowID.ToRequestParam("window_id"),
                                            other_price.ToRequestParam("other_price"),
                                            sms_price.ToRequestParam("sms_price"));

            return _baseWrapper.BeginGetResponse(_request, callback, state);
        }

        public bool EndOpenDialog(IAsyncResult asyncResult)
        {
            var _result = _baseWrapper.EndGetResponse(asyncResult);

            return (bool)_baseWrapper.ParsResponse(_result.Response, FuncType.PaymentsOpenDialog, _result.Request);
        }

        #endregion
#endif
        #endregion
    }

    internal struct AsyncResult
    {
        public String Response { get; set; }
        public String Request { get; set; }
    }

    #region Exceptions

    /// <summary>
    /// Внутренне исключение
    /// </summary>
    public class MailRuWrapperException : Exception
    {
        public MailRuWrapperException(string message)
            : base(message)
        {
        }

        public MailRuWrapperException(string message, Exception innerException)
            : base(message, innerException)
        {
        }
    }

    /// <summary>
    /// Исключение выбрасываемое при получении ответа API содержащего ошибку.
    /// </summary>
    public class MailRuWrapperRequestException : Exception
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="code">Код ошибки</param>
        /// <param name="message">Системное сообщение</param>
        /// <param name="request">Строка запроса</param>
        public MailRuWrapperRequestException(int code, string message, string request)
            : base(message)
        {
            Code = code;
            Request = request;
        }

        /// <summary>
        /// Код ошибки по спецификации API
        /// </summary>
        public int Code { get; private set; }

        /// <summary>
        /// Запрос на который была получена ошибка
        /// </summary>
        public String Request { get; set; }
    }

    /// <summary>
    /// Исключение выбрасываемое при ошибке валидации. Должно быть установлено свойство <see cref="BaseWrapper.IsPreValidationEnabled"/>
    /// </summary>
    public class MailRuWrapperValidationException : Exception
    {
        public MailRuWrapperValidationException(string message)
            : base(message)
        {
        }

        public MailRuWrapperValidationException(string message, Exception innerException)
            : base(message, innerException)
        {
        }
    }

    ///<summary>
    /// Fault исключение для передачи ошибок через сеть.
    /// <para>
    /// Для получения данного исключния из обычного используйте метод <see cref="Helper.ToFault(System.Exception)"/>
    /// </para>
    ///</summary>
    [DataContract]
    public class FaultMailRuWrapperException 
    {
        [DataMember]
        public String Message { get; set; }
    }

    ///<summary>
    /// Fault исключение для передачи ошибок через сеть.
    /// <para>
    /// Для получения данного исключния из обычного используйте метод <see cref="Helper.ToFault(MailRuWrapper.MailRuWrapperRequestException)"/>
    /// </para>
    ///</summary>
    [DataContract]
    public class FaultMailRuWrapperRequestException
    {
        /// <summary>
        /// Системное сообщение
        /// </summary>
        [DataMember]
        public String Message { get; set; }

        /// <summary>
        /// Запрос вызвавший ошибку
        /// </summary>
        [DataMember]
        public String Request { get; set; }

        /// <summary>
        /// Код ошибки
        /// </summary>
        [DataMember]
        public int Code { get; set; }
    }

    #endregion

    #region Interfaces

    /// <summary>
    /// Пользователь
    /// </summary>
    public interface IUser
    {
        /// <summary>
        /// Идентификатор пользователя
        /// </summary>
        string Uid { get; }
        /// <summary>
        /// Имя пользователя
        /// </summary>
        string FirstName { get; }
        /// <summary>
        /// Фамилия пользователя
        /// </summary>
        string LastName { get; }
        /// <summary>
        /// Никнейм пользователя
        /// </summary>
        string Nick { get; }
        /// <summary>
        /// Пол пользователя
        /// </summary>
        Gender Sex { get; }
        /// <summary>
        /// Дата рождения пользователя
        /// </summary>
        DateTime Birthday { get; }
        /// <summary>
        /// Аватар пользователя (размер по большей стороне не более 90 пикселов)
        /// </summary>
        Uri Pic { get; }
        /// <summary>
        /// Уменьшенный аватар пользователя (размер по большей стороне не более 45 пикселов)
        /// </summary>
        Uri PicSmall { get; }
        /// <summary>
        /// Большой аватар пользователя (размер по большей стороне не более 600 пикселов)
        /// </summary>
        Uri PicBig { get; }
        /// <summary>
        /// Адрес страницы пользователя на "Моем мире"
        /// </summary>
        Uri Link { get; }
        /// <summary>
        /// Тип реферера
        /// </summary>
        RefererTypes RefererType { get; }
        /// <summary>
        /// Идентификатор реферера
        /// </summary>
        String RefererId { get; }
        /// <summary>
        /// Ссылка на иконку статуса Агента пользователя
        /// </summary>
        Uri StatusAgent { get; }
        /// <summary>
        /// Почтовый адрес (e-mail)
        /// </summary>
        string MailAdress { get; }
        /// <summary>
        /// Полное имя пользователя. Имя и фамилия вместе
        /// </summary>
        string FullName { get; }
        /// <summary>
        /// Есть ли у пользователя referer_id
        /// </summary>
        bool IsReferred { get; }
    }

    /// <summary>
    /// Альбом пользователя
    /// </summary>
    public interface IPhotoAlbum
    {
        /// <summary>
        /// Идентификатор альбома пользователя
        /// </summary>
        string Aid { get; }
        /// <summary>
        /// Идентификатор фотографии, служащей обложкой альбома (пустое значение, если обложки нет)
        /// </summary>
        string CoverPid { get; }
        /// <summary>
        /// Идентификатор пользователя, владельца альбома
        /// </summary>
        string Owner { get; }
        /// <summary>
        /// Название альбома
        /// </summary>
        string Title { get; }
        /// <summary>
        /// Описание альбома
        /// </summary>
        string Description { get; }
        /// <summary>
        /// Время создания альбома
        /// </summary>
        DateTime Created { get; }
        /// <summary>
        /// Время последнего обновления альбома
        /// </summary>
        DateTime Updated { get; }
        /// <summary>
        /// Количество фотографий в альбоме
        /// </summary>
        int Size { get; }
        /// <summary>
        /// Уровень доступа к альбому <see cref="MailRuWrapper.Privacy"/>
        /// </summary>
        Privacy Privacy { get; }
    }

    /// <summary>
    /// Фотография пользователя
    /// </summary>
    public interface IPhoto
    {
        /// <summary>
        /// Идентификатор фотографии
        /// </summary>
        string Pid { get; }
        /// <summary>
        /// Идентификатор альбома, в котором находиться фотография
        /// </summary>
        string Aid { get; }
        /// <summary>
        /// Идентификатор пользователя, которому принадлежит альбом и фотография
        /// </summary>
        string Owner { get; }
        /// <summary>
        /// Путь к фотографии в стандартном размере (480px по большей стороне)
        /// </summary>
        Uri Src { get; }
        /// <summary>
        /// Путь к уменьшенной фотографии (120px по большей стороне)
        /// </summary>
        Uri SrcSmall { get; }
        /// <summary>
        /// Путь к фотографии в полном размере (отсутствует, если фотография не превышает 480px)
        /// </summary>
        Uri SrcBig { get; }
        /// <summary>
        /// Дата загрузки фотографии в альбом
        /// </summary>
        DateTime Created { get; }
        /// <summary>
        /// Ширина изображения в пикселах
        /// </summary>
        int Width { get; }
        /// <summary>
        /// Высота изображения в пикселах
        /// </summary>
        int Height { get; }
        /// <summary>
        /// Размер фотографии в байтах
        /// </summary>
        int Size { get; }
        /// <summary>
        /// Название фотографии
        /// </summary>
        string Title { get; }
    }

    /// <summary>
    /// Аудиозапись пользователя
    /// </summary>
    public interface IAudio
    {
        /// <summary>
        /// Идентификатор аудиозаписи
        /// </summary>
        string Mid { get; }
        /// <summary>
        /// Владелец аудиозаписи
        /// </summary>
        string Owner { get; }
        /// <summary>
        /// Исполнитель аудиозаписи
        /// </summary>
        string Artist { get; }
        /// <summary>
        /// Название аудиозаписи
        /// </summary>
        string Title { get; }
        /// <summary>
        /// Продолжительность аудиозаписи в секундах
        /// </summary>
        int Duration { get; }
        /// <summary>
        /// Hазмер аудиозаписи в байтах
        /// </summary>
        int Size { get; }
        /// <summary>
        /// Dремя загрузки аудиозаписи
        /// </summary>
        DateTime Created { get; }
        /// <summary>
        /// Cсылка на аудиозапись
        /// </summary>
        Uri Link { get; }
    }

    #endregion

    #region Enums

    /// <summary>
    /// Уровень доступа к альбому пользователя
    /// </summary>
    [DataContract(Name = "Privacy")]
    public enum Privacy
    {
        /// <summary>
        /// Открыт для всех
        /// </summary>
        [EnumMember]
        OpenToAll = 2,
        /// <summary>
        /// Доступ по паролю
        /// </summary>
        [EnumMember]
        PasswordAccess = 3,
        /// <summary>
        /// Только для друзей
        /// </summary>
        [EnumMember]
        OnlyFriends = 4,
        /// <summary>
        /// Только для лучших друзьям
        /// </summary>
        [EnumMember]
        OnlyBestFriends = 5,
        /// <summary>
        /// Только для друзей на Блогах
        /// </summary>
        [EnumMember]
        OnlyBlogsFriends = 6
    }

    /// <summary>
    /// Пол пользователя
    /// </summary>
    [DataContract(Name = "Gender")]
    public enum Gender
    {
        /// <summary>
        /// Мужской
        /// </summary>
        [EnumMember]
        Male = 0,
        /// <summary>
        /// Женский
        /// </summary>
        [EnumMember]
        Female = 1
    }

    /// <summary>
    /// Разрешения на выполнение действий для приложения.
    /// <para>
    /// Перечисление с аттрибутом [Flags]
    /// </para>
    /// </summary>
    [DataContract(Name = "AppPermission")]
    [Flags]
    public enum AppPermissionType
    {
        /// <summary>
        /// Нет значения
        /// </summary>
        [DefaultValue("none")]
        [EnumMember]
        None = 0,
        /// <summary>
        /// Разрешить публикацию новостей от приложения в блоке "Что нового?"
        /// </summary>
        [DefaultValue("stream")]
        [EnumMember]
        Stream = 1,
        /// <summary>
        /// Pазрешить присылать уведомления
        /// </summary>
        [DefaultValue("notifications")]
        [EnumMember]
        Notifications = 2,
        /// <summary>
        /// Разрешить публикацию виджета на странице профиля пользователя
        /// </summary>
        [DefaultValue("widget")]
        [EnumMember]
        Widget = 4
    }

    /// <summary>
    /// Тип формата ответа API
    /// </summary>
    public enum ApiFormat
    {
        /// <summary>
        /// JSON формат - по умолчанию
        /// </summary>
        [DefaultValue("json")]
        Json = 0,
        /// <summary>
        /// XML формат
        /// </summary>
        [DefaultValue("xml")]
        Xml = 1
    }

    /// <summary>
    /// Уровень детализации ведения журнала
    /// </summary>
    [Flags]
    public enum LogLevel
    {
        /// <summary>
        /// Журналирования не производится
        /// </summary>
        None = 0,
        /// <summary>
        /// Ошибки критического уровня
        /// </summary>
        Critical = 1,
        /// <summary>
        /// Ошибки уровня внутренней логики
        /// </summary>
        Wrapper = 2,
        /// <summary>
        /// Ошибки обработки запросов API
        /// </summary>
        Api = 4,
        /// <summary>
        /// Ошибки валидации
        /// </summary>
        Validation = 8,
        /// <summary>
        /// Все этапы
        /// </summary>
        Steps = 16,
        /// <summary>
        /// Выводятся все ошибки
        /// </summary>
        AllErrors = Critical | Wrapper | Api | Validation,
        /// <summary>
        /// Выводится все
        /// </summary>
        All = Critical | Wrapper | Api | Validation | Steps,
    }

    /// <summary>
    /// Тип referer'a
    /// </summary>
    public enum RefererTypes
    {
        /// <summary>
        /// Значение по умолчанию
        /// </summary>
        [DefaultValue("")]
        None,
        /// <summary>
        /// Сообщение об установке приложения другом
        /// </summary>
        [DefaultValue("stream.install")]
        StreamInstall,
        /// <summary>
        /// Сообщение о действиях друга в приложении
        /// </summary>
        [DefaultValue("stream.publish")]
        StreamPublish,
        /// <summary>
        /// Личное приглашение от друга
        /// </summary>
        [DefaultValue("invitation")]
        Invitation,
        /// <summary>
        /// Запись в списке приложений друга
        /// </summary>
        [DefaultValue("installed_apps")]
        InstalledApps,
        /// <summary>
        /// Описание приложения в каталоге приложений
        /// </summary>
        [DefaultValue("catalog")]
        Catalog,
        /// <summary>
        /// Блок "Mail.Ru рекомендует" или список приложений друзей в каталоге
        /// </summary>
        [DefaultValue("mailru_featured")]
        MailruFeatured
    }

    internal enum FuncType
    {
        [DefaultValue("friends.get")]
        FriendsGet = 0,
        [DefaultValue("friends.getAppUsers")]
        FriendsGetAppUsers = 1,
        [DefaultValue("users.getInfo")]
        UsersGetInfo = 2,
        [DefaultValue("users.isAppUser")]
        UsersIsAppUser = 3,
        [DefaultValue("users.hasAppPermission")]
        UsersHasAppPermission = 4,
        [DefaultValue("payments.openDialog")]
        PaymentsOpenDialog = 5,
        [DefaultValue("stream.publish")]
        StreamPublish = 6,
        [DefaultValue("photos.getAlbums")]
        PhotosGetAlbums = 7,
        [DefaultValue("photos.get")]
        PhotosGet = 8,
        [DefaultValue("audios.get")]
        AudiosGet = 9,
        [DefaultValue("audios.linkAudio")]
        AudiosLinkAudio = 10,
        [DefaultValue("notifications.send")]
        NotificationsSend = 11,
        [DefaultValue("widget.set")]
        WidgetSet = 12
    }


    #endregion

}
